﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using ManyConsole;
using NDesk.Options;
using TheTVDB;
using VideoReDo;
using ForTheRecord;
using NLog;

namespace Processor
{
   namespace ProcessorCommands
   {
      public class Clean : ConsoleCommand
      {
         public Clean()
         {
            this.IsCommand("Clean", "Removes all processed files.  (usually *.mp4 and *.edl)");
            this.SkipsCommandSummaryBeforeRunning();
            HasAdditionalArguments(1, "<Directory>");
         }

         public override int Run(string[] remainingArguments)
         {
            Console.WriteLine("Removal Directory: {0}", remainingArguments[0]);
            Console.WriteLine("Removal Filemask: *.{0} and *.{1}\n", Settings.CurrentSettings.VideoReDo.NewFilenameExtension, Settings.CurrentSettings.VideoReDo.EDLFilenameExtension);
            Console.Write("Are you SURE you want to proceed?  Type YES to proceed: ");
            if (Console.ReadLine().ToUpper() == "YES")
            {
               foreach (string s in Directory.GetFiles(remainingArguments[0], "*" + Settings.CurrentSettings.VideoReDo.NewFilenameExtension, SearchOption.AllDirectories))
               {
                  Console.WriteLine("  Deleting: {0}", s);
                  File.Delete(s);
               }

               foreach (string s in Directory.GetFiles(remainingArguments[0], "*" + Settings.CurrentSettings.VideoReDo.EDLFilenameExtension, SearchOption.AllDirectories))
               {
                  Console.WriteLine("  Deleting: {0}", s);
                  File.Delete(s);
               }

               Console.WriteLine("Finished.");
            }
            else
            {
               Console.WriteLine("Aborted.");
            }

            return 0;
         }
      }

      public class SaveDefaultSettings : ConsoleCommand
      {
         public SaveDefaultSettings()
         {
            this.IsCommand("SaveDefaultSettings", "Save the default settings to Settings.xml file.");
            this.SkipsCommandSummaryBeforeRunning();
         }

         public override int Run(string[] remainingArguments)
         {
            Console.WriteLine("Saving default settings...");
            Settings.CurrentSettings.Save("Settings.xml");
            Console.WriteLine("Settings have been saved to: Settings.xml");

            return 0;
         }
      }

      public class Process : ConsoleCommand
      {
         private static Logger Log = LogManager.GetCurrentClassLogger();

         public bool Recursive;
         public bool QuietMode;
         public bool DryRun;

         private VideoReDoAPI vrd;
         private TVDBApi tvdb;
         private FTR ftr;

         int totalFilesProcessed = 0;

         public Process()
         {
            this.IsCommand("Process", "Processes a file");
            this.SkipsCommandSummaryBeforeRunning();
            HasAdditionalArguments(1, "<FileName>");

            Options = new OptionSet() { 
               { "r|recursive", "Enables recursive file search", b => Recursive = b != null },
               { "d|dry", "Simulates performing actions", b => DryRun = b != null },           
            };
         }

         public override int Run(string[] remainingArguments)
         {
            string filename = remainingArguments[0];

            Console.WriteLine("Command-line: {0}\n", remainingArguments);
            Console.WriteLine("  VideoReDo QSF:  {0}", Settings.CurrentSettings.VideoReDo.QSFEnabled ? "[Enabled]" : "[disabled]");
            Console.WriteLine("  VideoReDo AdScan:    {0}", Settings.CurrentSettings.VideoReDo.EnableAdScan ? "[Enabled]" : "[disabled]");
            Console.WriteLine("  ForTheRecord:        {0}", Settings.CurrentSettings.ForTheRecord.Enabled ? "[Enabled]" : "[disabled]");
            Console.WriteLine("  ForTheRecord Update: {0}", Settings.CurrentSettings.ForTheRecord.UpdateFilename ? "[Enabled]" : "[disabled]");
            Console.WriteLine("  TVDB:                {0}", Settings.CurrentSettings.TVDB.RenameEnabled ? "[Enabled]" : "[disabled]");
            Console.WriteLine("  Delete Original:     {0}", Settings.CurrentSettings.DeleteOriginalFile ? "[Enabled] (CAUTION!)" : "[disabled]");

            Console.WriteLine();

            if ((Settings.CurrentSettings.TVDB.RenameEnabled) && (!(Settings.CurrentSettings.ForTheRecord.Enabled)))
            {
               Console.WriteLine("CRITICAL ERROR:  TVDB is enabled but For The Record is disabled.  This is not currently supported.");
               return 1;
            }

            if (Path.GetDirectoryName(filename) == string.Empty)
               filename = Path.Combine(Environment.CurrentDirectory, filename);

            if (filename.Contains("*"))
            {
               SearchOption so = Recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;

               int filesFound = 0;

               foreach (string file in Directory.GetFiles(Path.GetDirectoryName(filename), Path.GetFileName(filename), so))
               {
                  ProcessFile(file);
                  Console.WriteLine();
                  filesFound++;
               }

               if (filesFound == 0)
               {
                  Console.WriteLine("ERROR: No files found to process.");
                  return 1;
               }
            }
            else
            {
               if (!(File.Exists(filename)))
               {
                  Console.WriteLine("ERROR: File does not exist: {0}", Path.GetFileName(filename));
                  return 1;
               }

               ProcessFile(filename);
            }

            Log.Debug("Total files processed: {0}", totalFilesProcessed);

            return 0;
         }

         private Boolean ProcessFile(string filename)
         {
            Console.WriteLine("Processing: {0}", filename);

            string originalFilename = filename;
            string newFilename = filename;
            string edlFilename = string.Empty;
            string ftrFilename = string.Empty;

            // Initialize our VideoReDo, ForTheRecord, and TVDB objects
            if (!(InitializeObjects()))
            {
               Log.Error("CRITICAL ERROR:  InitializeObjects() returned false.");
               return false;
            }

            // =============================================================================================================
            //  VideoReDo QuickStreamFix
            // =============================================================================================================
            if (Settings.CurrentSettings.VideoReDo.QSFEnabled)
               if (!(ProcessVideoReDoQSF(originalFilename, ref newFilename)))
               {
                  FailureCleanup(originalFilename, newFilename, edlFilename);
                  return false;
               }

            // =============================================================================================================
            //  VideoReDo Ad Scanning
            // =============================================================================================================
            if (Settings.CurrentSettings.VideoReDo.EnableAdScan)
               if (!(ProcessVideoReDoAdScan(newFilename, ref edlFilename)))
               {
                  FailureCleanup(originalFilename, newFilename, edlFilename);
                  return false;
               }

            // =============================================================================================================
            //  TVDB/ForTheRecord - Obtain FTR DB recording filename since we will need it a lot below
            // =============================================================================================================
            if ((Settings.CurrentSettings.TVDB.RenameEnabled) || (Settings.CurrentSettings.ForTheRecord.Enabled))
               if (!(ProcessTVDBFTRFilename(originalFilename, ref ftrFilename)))
               {
                  FailureCleanup(originalFilename, newFilename, edlFilename);
                  return false;
               }

            // =============================================================================================================
            //  TVDB Renaming
            // =============================================================================================================
            if (Settings.CurrentSettings.TVDB.RenameEnabled)
            {
               string tvdbFilename = string.Empty;

               if (!(ProcessTVDB(newFilename, ftrFilename, ref tvdbFilename)))
                  Console.WriteLine("  WARNING: TVDB renaming failed (or no matches found), but continuing...");
               else
               {
                  // Rename/Move our file  (NOTE: This could be our original file if no VRD QSF was performed!)
                  File.Move(newFilename, tvdbFilename);
                  newFilename = tvdbFilename;

                  if ((edlFilename != string.Empty) && (File.Exists(edlFilename)))
                  {
                     string newEdlFilename = Path.Combine(Path.GetDirectoryName(edlFilename), Path.GetFileNameWithoutExtension(tvdbFilename) + Settings.CurrentSettings.VideoReDo.EDLFilenameExtension);
                     File.Move(edlFilename, newEdlFilename);
                     edlFilename = newEdlFilename;
                  }
               }
            }

            // =============================================================================================================
            //  For The Record Processing
            // =============================================================================================================
            if ((Settings.CurrentSettings.ForTheRecord.Enabled) && (Settings.CurrentSettings.ForTheRecord.UpdateFilename))
            {
               if (!(ProcessFTR(ftrFilename, newFilename)))
               {
                  FailureCleanup(originalFilename, newFilename, edlFilename);
                  return false;
               }
            }

            // =============================================================================================================
            //  Delete original file if settings allow
            // =============================================================================================================
            if ((originalFilename != newFilename) && (Settings.CurrentSettings.DeleteOriginalFile))
            {
               File.Delete(originalFilename);
            }

            totalFilesProcessed++;

            Console.WriteLine("  Finished processing file.\n");

            return true;
         }

         private Boolean InitializeObjects()
         {
            Console.WriteLine("  Initializing objects... (VideoReDo, ForTheRecord, TVDB, etc)");

            if ((vrd == null) && ((Settings.CurrentSettings.VideoReDo.QSFEnabled) || (Settings.CurrentSettings.VideoReDo.EnableAdScan)))
               try
               {
                  vrd = new VideoReDoAPI();
                  if (vrd.Version() == string.Empty)
                     throw new Exception();
               }
               catch
               {
                  Console.WriteLine("CRITICAL ERROR.  Unable to start VideoReDo server!  Aborting.\n");
                  vrd = null;
                  return false;
               }

            if ((ftr == null) && (Settings.CurrentSettings.ForTheRecord.Enabled))
            {
               ftr = new FTR();
               ftr.ServerName = Settings.CurrentSettings.ForTheRecord.ServerName;
               ftr.ServerPort = Settings.CurrentSettings.ForTheRecord.ServerPort;

               if (!(ftr.Connect()))
               {
                  Console.WriteLine("  CRITICAL ERROR.  Unable to connect to For The Record server!  Aborting.\n");
                  ftr = null;
                  return false;
               }
            }

            if ((tvdb == null) && (Settings.CurrentSettings.TVDB.RenameEnabled))
               try
               {
                  tvdb = new TVDBApi();
               }
               catch
               {
                  Console.WriteLine("  CRITICAL ERROR.  Unable to initialize TVDB API!  Aborting.\n");
                  tvdb = null;
                  return false;
               }

            return true;
         }

         private Boolean ProcessVideoReDoQSF(string originalFilename, ref string newFilename)
         {
            try
            {

               if (!(vrd.FileOpen(originalFilename)))
               {
                  Console.WriteLine("  ERROR!  Unable to open file with VideoReDo: {0}\n", Path.GetFileName(originalFilename));
                  return false;
               }

               newFilename = Path.ChangeExtension(originalFilename, Settings.CurrentSettings.VideoReDo.NewFilenameExtension);

               Console.WriteLine("  Saving QuickStreamFixed file...");
               vrd.FileSave_Block(newFilename, Settings.CurrentSettings.VideoReDo.ProfileName);
               vrd.FileClose();

               if (!(CheckFiles(originalFilename, newFilename)))
               {
                  return false;
               }

               return true;
            }
            catch (Exception ex)
            {
               Log.ErrorException("ERROR: Exception occured in ProcessVideoReDoQSF.  Aborting.", ex);
               return false;
            }
         }

         private Boolean ProcessVideoReDoAdScan(string newFilename, ref string edlFilename)
         {
            try
            {
               edlFilename = Path.ChangeExtension(newFilename, Settings.CurrentSettings.VideoReDo.EDLFilenameExtension);

               Boolean foundAdvertisements = false;

               vrd.FileOpen(newFilename);
               foundAdvertisements = vrd.AdScan_Block();

               if (foundAdvertisements)
               {
                  vrd.SaveEDL(edlFilename);
               }
               else
               {
                  Console.WriteLine("    Skipping EDL file... (no advertisements found)");

                  if (File.Exists(edlFilename))
                     File.Delete(edlFilename);
               }

               vrd.FileClose();

               return true;
            }
            catch (Exception ex)
            {
               Log.ErrorException("ERROR: Exception occured in ProcessVideoReDoAdScan.  Aborting.", ex);
               return false;
            }
         }

         private Boolean ProcessTVDBFTRFilename(string originalFilename, ref string ftrFilename)
         {
            try
            {
               Console.WriteLine("  Retrieving ForTheRecord recording filename...");
               ftrFilename = ftr.GetRecordingFilename(Path.GetFileName(originalFilename));

               if (ftrFilename == string.Empty)
               {
                  Console.WriteLine("    ERROR! Unable to retrieve ForTheRecord recording filename. Aborting.");
                  return false;
               }
               else
                  return true;
            }
            catch (Exception ex)
            {
               Log.ErrorException("ERROR: Exception occured in ProcessTVDBFTRFilename.  Aborting.", ex);
               return false;
            }
         }

         private Boolean ProcessTVDB(string newFilename, string ftrFilename, ref string tvdbFilename)
         {
            try
            {
               Console.WriteLine("  Checking TVDB for correct episode...");

               // get schedule name for recording
               string programTitle = ftr.GetProgramTitleFromRecordingFilename(ftrFilename);

               // this shouldn't happen because it should be caught earlier
               if (programTitle == string.Empty)
               {
                  Console.WriteLine("    ERROR: Unable to find recording program title!  (FTR Recording doesn't exist?)");
                  return false;
               }

               // We require a mapping for this version of the program
               int tvdbID = Settings.CurrentSettings.FindProgramMapping(programTitle);

               if (tvdbID == -1)
               {
                  Console.WriteLine("    Unable to find recording program title!  (Check ProgramMappings)");
                  return false;
               }

               // Get information from FTR about this recording
               string epTitle = string.Empty;
               string epSubtitle = string.Empty; // what we use for lookups
               int? epSeries = null;
               int? epEpisode = null;

               if (ftr.GetRecordingInformation(ftrFilename, ref epTitle, ref epSubtitle, ref epSeries, ref epEpisode))
               {
                  // found valid information
                  string newEpTitle = string.Empty;
                  int newSeasonNumber = 0;
                  int newEpisodeNumber = 0;

                  Console.WriteLine("    Searching for: [{0}] - \"{1}\"", programTitle, epSubtitle);

                  if (tvdb.GetSeasonAndEpisode(tvdb.GetSeriesByID(tvdbID), epSubtitle, ref newSeasonNumber, ref newEpisodeNumber, ref newEpTitle))
                  {
                     Console.WriteLine("    Match found!  Season: [{0}] Episode: [{1}] Name: [{2}]", newSeasonNumber.ToString("00"), newEpisodeNumber.ToString("00"), newEpTitle);

                     tvdbFilename = Settings.CurrentSettings.TVDB.RenamedFileFormat;

                     tvdbFilename = tvdbFilename.Replace("{Title}", epTitle);
                     tvdbFilename = tvdbFilename.Replace("{EpisodeName}", newEpTitle);

                     if (Settings.CurrentSettings.TVDB.TwoDigitNumerics)
                     {
                        tvdbFilename = tvdbFilename.Replace("{SeasonNumber}", newSeasonNumber.ToString("00"));
                        tvdbFilename = tvdbFilename.Replace("{EpisodeNumber}", newEpisodeNumber.ToString("00"));
                     }
                     else
                     {
                        tvdbFilename = tvdbFilename.Replace("{SeasonNumber}", newSeasonNumber.ToString());
                        tvdbFilename = tvdbFilename.Replace("{EpisodeNumber}", newEpisodeNumber.ToString());
                     }

                     // Add extension
                     tvdbFilename += Path.GetExtension(newFilename);

                     tvdbFilename = Path.Combine(Path.GetDirectoryName(newFilename), tvdbFilename);

                     Console.WriteLine("    New filename: {0}", Path.GetFileName(tvdbFilename));

                     try
                     {
                        if (ftr.UpdateRecordingEpisodeAndSeason(ftrFilename, newSeasonNumber, newEpisodeNumber, newEpTitle))
                           Console.WriteLine("    Updated ForTheRecord DB with season/episode information.");
                        else
                           Console.WriteLine("    WARNING: Failed to update ForTheRecord DB with season/episode information.");
                     }
                     catch
                     {
                        Console.WriteLine("    WARNING: Unknown error while trying to update ForTheRecord DB with season/episode information.");
                     }

                     return true;
                  }
                  else
                  {
                     Console.WriteLine("    No matches found on TVDB.");
                     return false;
                  }
               }
               else
               {
                  Console.WriteLine("    ERROR: Unable to retrieve recording information!");
                  return false;
               }

            }
            catch (Exception ex)
            {
               Log.ErrorException("ERROR: Exception occured in ProcessTVDB.", ex);
               return false;
            }

         }

         private Boolean ProcessFTR(string recordingFilename, string newFilename)
         {
            try
            {
               Console.WriteLine("  Updating For The Record filename...");

               if (!(ftr.UpdateRecordingFilename(recordingFilename, newFilename)))
               {
                  Console.WriteLine("    Update failed. Aborting.");
                  return false;
               }
               else
                  Console.WriteLine("    Successfully updated recording filename.");

               return true;
            }
            catch (Exception ex)
            {
               Log.ErrorException("ERROR: Exception occured in ProcessFTR.  Aborting.", ex);
               return false;
            }
         }

         #region Check File Sizes/Failure Cleanup
         public Boolean CheckFiles(string oldFilename, string newFilename)
         {
            FileInfo fiOld = new FileInfo(oldFilename);
            FileInfo fiNew = new FileInfo(newFilename);

            double difference = ((double)fiOld.Length / (double)fiNew.Length) / 100;

            if (difference > Settings.CurrentSettings.VideoReDo.VerifyThreshold)
            {
               Console.WriteLine("    ERROR: Verification of new file size was failed. ({0}% difference)", (difference * 100).ToString("0"));
               return false;
            }
            else
            {
               Console.WriteLine("    Verification of new file size was successful. ({0}% difference)", (difference * 100).ToString("0"));
               return true;
            }
         }

         private void FailureCleanup(string originalFilename, string newFilename, string edlFilename)
         {
            if (originalFilename != newFilename)
            {
               if (File.Exists(newFilename))
                  File.Delete(newFilename);
            }

            if (File.Exists(edlFilename))
               File.Delete(edlFilename);
         }
         #endregion
      }
   }
}
